#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <math.h>
#include <time.h>
#include "Movie_Person.h"

using namespace std;

void ReadDatasetUsingOnlyMap(const char *file, vector<Person> &persons, unordered_map<string, Movie> &movieMap);
void addGenre(const char *file, vector<Person> &persons, unordered_map<string, Movie> &movies);
void findPopularActors(vector<Person> persons, vector<int> &popularActors, int nActors, unordered_map<string, Movie> movies);
void WriteToCSV(const char *file, vector<Person> persons, unordered_map<string, Movie> movies, vector<int> popularActors, bool skipMoviesZeroPopular, bool actorColumns);

void main(){
	time_t startTime = time(NULL);
	vector<Person> persons;
	unordered_map<string, Movie> movieMap;
	vector<int> popularActors; // int because the vector points to indices of the persons vector
	ReadDatasetUsingOnlyMap("actors_medium.txt", persons, movieMap);
	addGenre("genres.txt", persons, movieMap);
	findPopularActors(persons, popularActors, 10, movieMap);
	WriteToCSV("actors_medium.csv", persons, movieMap, popularActors, true, true);
	time_t endTime = time(NULL);
	cout << "Total time of completion: " << endTime - startTime << " seconds.\n";
	system("pause");
}

void ReadDatasetUsingOnlyMap(const char *file, vector<Person> &persons, unordered_map<string, Movie> &movieMap){
	cout << "Reading data from '" << file << "'\n";
	cout << "Lines read: ";
	time_t startTime = time(NULL);
	string name = "", title = "", year = "";
	bool justReadBlank = false, actorsNowFull = false; // justReadBlank indicates that a blank line previously was read, which means that we now expect a new actor
	int minMovieAmount = 1000, minMovieIndex = -1, linesRead = 0;
	string line;
	ifstream in(file);
	while(!in.eof()){
		getline(in, line);
		if(!justReadBlank){ // If we didn't just read a blank line, we're either currently reading a blank line, or we're reading one of the movies of an actor
			if(line.size() == 0)
				justReadBlank = true;
			else if(persons.size() > 0 && line.find('"') == string::npos){ // If there is a person in the list, and the list does not contain ", a TV episode, we assume it's a movie - find() returns string::npos, if the provided char is not found
				title = line.substr(0, line.find('(') - 1); // Movies are listed as: Title (Year) and then sometimes some weird stuff, which we dont care about. We assume that the characters until the ( contain the title
				while(title.find(9) != string::npos) // If the newly read title string contains charcode 9, a horizontal tab, we remove it from the string
					title.erase(title.find(9), 1);
				while(title.find(',') != string::npos) // We also remove commas
					title.erase(title.find(','), 1);
				while(title.find(39) != string::npos) // 39 is an abostroph, which we also don't like
					title.erase(title.find(39), 1);
				while(title.find('%') != string::npos) // %'s are removed aswell.
					title.erase(title.find('%'), 1);
				year = line.substr(line.find('(') + 1, 4); // We assume that the year starts after ( and has 4 digits
				stringstream ss2(year); // Stringstream is used to convert the string into an integer
				int yearNumber = 0;
				ss2 >> setprecision(0) >> yearNumber;
				if(yearNumber >= 1990 && yearNumber < 2000){ // We only care about movies from the 90's
					unordered_map<string, Movie>::const_iterator got = movieMap.find(title + year); // We use this hash map to instantly search for whether the movie already has been read.
					if(got == movieMap.end()){ // If not, we create a new object of the Movie class
						Movie movie;
						movie.setTitle(title);
						movie.setYear(yearNumber);
						movie.addActor(persons.size() - 1); // Similarly, the current actor is added to the newly created movie object
						persons.back().addMovie(title + year); // addMovie adds the vector index of the movie in the Movie vector to the current person's vector of movie indices, as well as the hash map of the person
						movieMap[title + year] = movie;
					}
					else{
						persons.back().addMovie(got->first); // If the movie already exists, its index is hold in the second value of the iterator used upon the hash map
						movieMap[title + year].addActor(persons.size() - 1); // The current actor is also added to that movie's person-index vector
					}
				}
			}
		}
		else if(justReadBlank){ // If we just read a blank line we know that a new actor is incoming
			justReadBlank = false;
			string surName;
			string givenNames;
			if(line.find(',') != string::npos){ // Normally, a comma seperates the sur name from the given names
				surName = line.substr(0, line.find(','));
				givenNames = line.substr(line.find(',') + 1, line.find(9) - (line.find(',') + 1)); // The given names end with one or more horizontal tabs before the title starts
			}
			else{ // If the name does not contain a comma, we simply make the surName string empty and set givenNames to the chars until the first horizontal tab
				surName = "";
				givenNames = line.substr(0, line.find(9));
			}
			Person person;
			person.setSurName(surName);
			person.setGivenNames(givenNames);
			persons.push_back(person);
			if(line.find('"') == string::npos){ // This is pretty much the same as above in the if(!justReadBlank) statement - here the first movie title, which is on the same line as the actor names, is read.
				int titleStart = line.find(9) + 1;
				int titleSize = line.find('(') - (line.find(9) + 2);
				int altTitleSize = line.substr(line.find(9), string::npos).find('(') - 2;
				title = line.substr(titleStart, titleSize > 0 ? titleSize : altTitleSize);
				while(title.find(9) != string::npos)
					title.erase(title.find(9), 1);
				while(title.find(',') != string::npos)
					title.erase(title.find(','), 1);
				while(title.find(39) != string::npos)
					title.erase(title.find(39), 1);
				while(title.find('%') != string::npos)
					title.erase(title.find('%'), 1);
				year = line.substr(titleSize > 0 ? line.find('(') + 1 : titleStart + 2 + altTitleSize, 4);
				stringstream ss(year);
				int yearNumber = 0;
				ss >> setprecision(0) >> yearNumber;
				if(yearNumber >= 1990 && yearNumber < 2000){
					unordered_map<string, Movie>::const_iterator got = movieMap.find(title + year);
					if(got == movieMap.end()){ // If not, we create a new object of the Movie class
						Movie movie;
						movie.setTitle(title);
						movie.setYear(yearNumber);
						movie.addActor(persons.size() - 1); // Similarly, the current actor is added to the newly created movie object
						persons.back().addMovie(title + year); // addMovie adds the vector index of the movie in the Movie vector to the current person's vector of movie indices, as well as the hash map of the person
						movieMap[title + year] = movie;
					}
					else{
						persons.back().addMovie(got->first); // If the movie already exists, its index is hold in the second value of the iterator used upon the hash map
						movieMap[title + year].addActor(persons.size() - 1); // The current actor is also added to that movie's person-index vector
					}
				}
			}
		}
		linesRead++;
		if(linesRead % 10000 == 0)
			cout << linesRead << " ";
	}
	in.close();
	time_t endTime = time(NULL);
	cout << "\nReading of '" << file <<"' is complete. It took " << endTime - startTime << " seconds.\n\n\n";
}

void addGenre(const char *file, vector<Person> &persons, unordered_map<string, Movie> &movies){
	cout << "Adding genres from '" << file << "'\n";
	cout << "Lines read: ";
	time_t startTime = time(NULL);
	string line, title, year, genre, prevTitleYear = "", skipThis = "";
	int linesRead = 0;
	ifstream in(file);
	while(!in.eof()){	
		getline(in, line);
		if(line.find('"') == string::npos){
			title = line.substr(0, line.find('(') - 1); // Movies are listed as: Title (Year) and then sometimes some weird stuff, which we dont care about. We assume that the characters until the ( contain the title
			while(title.find(9) != string::npos) // If the newly read title string contains charcode 9, a horizontal tab, we remove it from the string
				title.erase(title.find(9), 1);
			while(title.find(',') != string::npos) // We also remove commas
				title.erase(title.find(','), 1);
			while(title.find(39) != string::npos) // 39 is an abostroph, which we also don't like
				title.erase(title.find(39), 1);
			while(title.find('%') != string::npos) // %'s are removed aswell.
				title.erase(title.find('%'), 1);
			year = line.substr(line.find('(') + 1, 4); // We assume that the year starts after ( and has 4 digits
			stringstream ss2(year); // Stringstream is used to convert the string into an integer
			int yearNumber = 0;
			ss2 >> setprecision(0) >> yearNumber;
			if(yearNumber >= 1990 && yearNumber < 2000){ // We only care about movies from the 90's
				if(prevTitleYear == title + year){
					if(prevTitleYear != skipThis){
						genre = line.substr(line.find(9) + 1, line.size() - line.find(9) - 1);
						while(genre.find(9) != string::npos) // If the newly read title string contains charcode 9, a horizontal tab, we remove it from the string
							genre.erase(genre.find(9), 1);
						if(genre.find("Adult") != string::npos || genre.find("Short") != string::npos || genre.find("Lifestyle") != string::npos){
							movies[title + year].removeFromActorMap(persons, title + year);	
							movies.erase(prevTitleYear);
							skipThis = prevTitleYear;
						}
						else
							movies[prevTitleYear].addGenre(genre);
					}
				}
				else{
					unordered_map<string, Movie>::const_iterator got = movies.find(title + year); // We use this hash map to instantly search for whether the movie already has been read.
					if(got != movies.end()){ 
						genre = line.substr(line.find(9) + 1, line.size() - line.find(9) - 1);
						while(genre.find(9) != string::npos) // If the newly read title string contains charcode 9, a horizontal tab, we remove it from the string
							genre.erase(genre.find(9), 1);
						if(genre.find("Adult") != string::npos || genre.find("Short") != string::npos || genre.find("Lifestyle") != string::npos){
							movies[title + year].removeFromActorMap(persons, title + year);
							movies.erase(title + year);
							prevTitleYear = title + year;
							skipThis = prevTitleYear;
						}
						else{
							movies[title + year].setGenre(genre);
							prevTitleYear = title + year;
						}
					}
				}
			}
		}
		linesRead++;
		if(linesRead % 50000 == 0)
			cout << linesRead << " ";
	}
	in.close();
	in.close();
	time_t endTime = time(NULL);
	cout << "\nReading of '" << file <<"' is complete. It took " << endTime - startTime << " seconds.\n\n\n";
}

void findPopularActors(vector<Person> persons, vector<int> &popularActors, int nActors, unordered_map<string, Movie> movies){
	cout << "Finding " << nActors << " popular actors, among " << persons.size() << " actors.\n";
	cout << "Actors read: ";
	time_t startTime = time(NULL);
	int minMovieAmount = 1000;
	for(int i = 0; i < persons.size(); i++){
		int curMovieAmount = persons[i].movieAmount();
		if(popularActors.size() < nActors){ // While the popular actor vector is not full (determined by the function parameter nActors, we simply add the first nActor actors to the vector
			popularActors.push_back(i);
			if(curMovieAmount < minMovieAmount)
				minMovieAmount = curMovieAmount;
		}
		else if(curMovieAmount > minMovieAmount){ // Otherwise, we check if the amount of movies that the current person has starred in, is higher than the minimum number from the popularActor vector
			bool newPopular = false; // This bool is used to see if the current person is popular, since we then also need to update the minMovieAmount variable
			for(int j = 0; j < popularActors.size(); j++){
				if(persons[popularActors[j]].movieAmount() < curMovieAmount){
					newPopular = true;
					popularActors[j] = i;
					break;
				}
			}
			if(!newPopular)
				minMovieAmount = curMovieAmount;
		}
		if(i % 10000 == 0)
			cout << i << " ";
	}
	time_t endTime = time(NULL);
	cout << "\nSearch for popular actors is done. It took " << endTime - startTime << " seconds.\n\n\n";
}

void WriteToCSV(const char *file, vector<Person> persons, unordered_map<string, Movie> movies, vector<int> popularActors, bool skipMoviesZeroPopular, bool actorColumns){
	cout << "Writing " << movies.size() << " movies to '" << file << "'\n";
	cout << "Lines written: ";
	time_t startTime = time(NULL);
	int linesWritten = 0, actorCount = 0, counter = 0;
	ofstream out(file);
	out << "Title and year, "; // The first line should hold the attribute names, in this case title and year are merged into one.
	if(!actorColumns)
		out << "Genre(s), ";
	else
		out << "Drama?, Comedy?, Documentary?, Romance?, Action?, Animation?, Thriller?, Family?, Crime?, Musical?, Adventure?, ";
	for(int j = 0; j < popularActors.size(); j++){  // The rest of the attributes are the popular actors
		if(j < popularActors.size() - 1)
			out << persons[popularActors[j]].getGivenNames() << " " << persons[popularActors[j]].getSurName() << "?, ";
		else
			out << persons[popularActors[j]].getGivenNames() << " " << persons[popularActors[j]].getSurName() << "?";
	}
	out << "\n";
	for(auto i = movies.begin(); i != movies.end(); ++i){ // We then procced to go through all of the movies
		counter++;
		if(movies[i->first].getGenre() != ""){
			out << movies[i->first].getTitle() << " " << movies[i->first].getYear() << ", "; // Title and year are written as the first value, followed by a comma
			if(!actorColumns)
				out << movies[i->first].getGenre() << ", ";
			else{
				string genre = movies[i->first].getGenre();
				out << (genre.find("Drama") != string::npos ? "T, " : "F, ");
				out << (genre.find("Comedy") != string::npos ? "T, " : "F, ");
				out << (genre.find("Documentary") != string::npos ? "T, " : "F, ");
				out << (genre.find("Romance") != string::npos ? "T, " : "F, ");
				out << (genre.find("Action") != string::npos ? "T, " : "F, ");
				out << (genre.find("Animation") != string::npos ? "T, " : "F, ");
				out << (genre.find("Thriller") != string::npos ? "T, " : "F, ");
				out << (genre.find("Family") != string::npos ? "T, " : "F, ");
				out << (genre.find("Crime") != string::npos ? "T, " : "F, ");
				out << (genre.find("Musical") != string::npos ? "T, " : "F, ");
				out << (genre.find("Adventure") != string::npos ? "T, " : "F, ");
			}
			actorCount = 0;
			for(int k = 0; k < popularActors.size(); k++){ // Then, we go through all the popular actors and use actorInMovie() to tell whether or not the movie has starred in the current movie
				if(k < popularActors.size() - 1){
					if(movies[i->first].actorInMovie(popularActors[k])){
						actorCount++;
						out << "T, ";
					}
					else
						out << "F, ";
				}
				else{
					if(movies[i->first].actorInMovie(popularActors[k])){
						actorCount++;
						out << "T";
					}
					else
						out << "F";
				}
			}
			if(skipMoviesZeroPopular){
				if(actorCount == 0){
					long pos = out.tellp();
					pos -= movies[i->first].getTitle().size();
					pos -= actorColumns ? 38 : (movies[i->first].getGenre().size() + 7);
					pos -= popularActors.size() * 3;
					out.seekp(pos);
					linesWritten--;
					if(counter == movies.size())
						out << "INVALID - DELETE THIS LINE MANUALLY!     ";
				}
				else{
					out << "\n";
					//if(actorCount > 1)
					//	cout << movies[i->first].getTitle() << " has " << actorCount << " popular actors!\n";
				}
			}
			else
				out << "\n";
		}
		linesWritten++;
		if(linesWritten % 10000 == 0)
			cout << linesWritten << " ";
	}
	out.close();
	time_t endTime = time(NULL);
	cout << "\nWriting to '" << file <<"' is complete. It took " << endTime - startTime << " seconds.\n\n\n";
}



